package com.googlecode.gwtbb.server.intermediate.picture;

import com.googlecode.gwtbb.client.shared.GwtbbSmess;
import com.googlecode.gwtbb.client.shared.entity.picture.*;
import com.googlecode.gwtbb.client.shared.entity.user.User;
import com.googlecode.gwtbb.client.shared.remote.picture.PictureItemBean;
import com.googlecode.gwtbb.client.shared.util.StringUtils;
import com.googlecode.gwtbb.client.shared.validation.Failure;
import com.googlecode.gwtbb.client.shared.validation.FieldId;
import com.googlecode.gwtbb.server.dao.picture.PictureDao;
import com.googlecode.gwtbb.server.dao.picture.PictureSnapDao;
import com.googlecode.gwtbb.server.dao.picture.ThumbDao;
import com.googlecode.gwtbb.server.dao.picture.ThumbSnapDao;
import com.googlecode.gwtbb.server.intermediate.Meta;

import java.util.*;

/**
 * @author Abraham Grief
 * @version $Id$
 * @since Aug 3, 2008
 */
public class PictureVip {
	private PictureDao pictureDao;
	private PictureSnapDao pictureSnapDao;
	private ThumbDao thumbDao;
	private ThumbSnapDao thumbSnapDao;
	private PictureFileOrganizer pictureFileOrganizer;

	public PictureDao getPictureDao() {
		return pictureDao;
	}

	public void setPictureDao(PictureDao pictureDao) {
		this.pictureDao = pictureDao;
	}

	public PictureFileOrganizer getPictureFileOrganizer() {
		return pictureFileOrganizer;
	}

	public void setPictureFileOrganizer(PictureFileOrganizer pictureFileOrganizer) {
		this.pictureFileOrganizer = pictureFileOrganizer;
	}

	public PictureSnapDao getPictureSnapDao() {
		return pictureSnapDao;
	}

	public void setPictureSnapDao(PictureSnapDao pictureSnapDao) {
		this.pictureSnapDao = pictureSnapDao;
	}

	public ThumbDao getThumbDao() {
		return thumbDao;
	}

	public void setThumbDao(ThumbDao thumbDao) {
		this.thumbDao = thumbDao;
	}

	public ThumbSnapDao getThumbSnapDao() {
		return thumbSnapDao;
	}

	public void setThumbSnapDao(ThumbSnapDao thumbSnapDao) {
		this.thumbSnapDao = thumbSnapDao;
	}

	public void insist(CreateState state, Date snapDate, User actor, String process) {
		instantiate(state, snapDate, actor, process);
		persist(state);
	}

	public void insist(UpdateState state, Date snapDate, User actor, String process) {
		instantiate(state, snapDate, actor, process);
		persist(state);
	}

	public void instantiate(CreateState state, Date snapDate, User actor, String process) {
		if (state.isInstantiatable()) {
			List<Picture> orderedPictures = new ArrayList<Picture>(state.numPictures);
			List<Picture> createPictures = new ArrayList<Picture>(state.numPictures);
			List<PictureSnap> pictureSnaps = new ArrayList<PictureSnap>(state.numPictures);
			List<Thumb> createThumbs = new ArrayList<Thumb>(state.numPictures * 2);
			List<ThumbSnap> thumbSnaps = new ArrayList<ThumbSnap>(state.numPictures * 2);

			for (int i = 0; i < state.pictureItemsSize; i++) {
				PictureItemBean pib = state.pictureItems.get(i);
				String fileName = pib.getFileName();
				if (StringUtils.isNotEmpty(fileName)) {
					Picture picture = new Picture();
					pictureFileOrganizer.fillPicture(picture, fileName);
					picture.setCaption(pib.getCaption());
					picture.setSnapDate(snapDate);
					orderedPictures.add(picture);
					createPictures.add(picture);
					pictureSnaps.add(newPictureSnapshot(picture, actor, process));

					Collection<Thumb> picThumbs = pictureFileOrganizer.fillThumbs(fileName);
					for (Thumb picThumb : picThumbs) {
						picThumb.setThumbId(thumbDao.generateId());
						picThumb.setSnapDate(snapDate);
						createThumbs.add(picThumb);
						thumbSnaps.add(newThumbSnapshot(picThumb, actor, process));
					}
				}
			}

			state.orderedPictures = orderedPictures;
			state.createPictures = createPictures;
			state.pictureSnaps = pictureSnaps;
			state.createThumbs = createThumbs;
			state.thumbSnaps = thumbSnaps;
		}
	}

	public void instantiate(UpdateState state, Date snapDate, User actor, String process) {
		if (state.isInstantiatable()) {
			List<Picture> orderedPictures = new ArrayList<Picture>(state.numPictures);
			List<Picture> createPictures = new ArrayList<Picture>(state.numPictures);
			List<Map<String, Object>> updatePictures = new ArrayList<Map<String, Object>>(state.numPictures);
			List<PictureSnap> pictureSnaps = new ArrayList<PictureSnap>(state.numPictures);
			List<Thumb> createThumbs = new ArrayList<Thumb>(state.numPictures * 2);
			List<Map<String, Object>> updateThumbs = new ArrayList<Map<String, Object>>(state.numPictures * 2);
			List<ThumbSnap> thumbSnaps = new ArrayList<ThumbSnap>(state.numPictures * 2);
			for (int i = 0; i < state.pictureItemsSize; i++) {
				PictureItemBean pib = state.pictureItems.get(i);
				String fileName = pib.getFileName();
				if (StringUtils.isNotEmpty(fileName)) {
					Picture picture = new Picture();
					pictureFileOrganizer.fillPicture(picture, fileName);
					picture.setCaption(pib.getCaption());
					picture.setSnapDate(snapDate);
					Long pictureId = picture.getId();
					Picture dbPicture = pictureDao.select(pictureId);

					// it's in ordered pictures regardless of whether there were changes to it or not
					orderedPictures.add(picture);
					if (dbPicture == null) {
						// if it doesn't currently exist in the db, we'll have to create it
						createPictures.add(picture);
						pictureSnaps.add(newPictureSnapshot(picture, actor, process));
					} else {
						// if it does exist, check if any substantial properties have changed that we need to update
						Map<String, Object> updateMap = Meta.instantiateUpdateMap(dbPicture, picture, Picture.UPDATE_PROPERTIES, snapDate);
						if (!updateMap.isEmpty()) {
							updatePictures.add(updateMap);
							pictureSnaps.add(newPictureSnapshot(picture, actor, process));
						}
					}

					Collection<Thumb> picThumbs = pictureFileOrganizer.fillThumbs(fileName);
					Collection<Thumb> dbThumbs = thumbDao.selectByPictureId(pictureId);
					Map<String, Thumb> urlThumbs = new HashMap<String, Thumb>(dbThumbs.size());
					for (Thumb dbThumb : dbThumbs) {
						urlThumbs.put(dbThumb.getUrl(), dbThumb);
					}
					for (Thumb picThumb : picThumbs) {
						Thumb dbThumb = urlThumbs.remove(picThumb.getUrl());
						if (dbThumb == null) {
							picThumb.setThumbId(thumbDao.generateId());
							picThumb.setSnapDate(snapDate);
							createThumbs.add(picThumb);
						} else {
							picThumb.setThumbId(dbThumb.getId());
							picThumb.setSnapDate(snapDate);
							Map<String, Object> updateMap = Meta.instantiateUpdateMap(dbThumb, picThumb, Thumb.UPDATE_PROPERTIES, snapDate);
							if (!updateMap.isEmpty()) {
								updateThumbs.add(updateMap);
								thumbSnaps.add(newThumbSnapshot(picThumb, actor, process));
							}
						}
					}
				}
				// todo delete thumbs that no longer exist... we have no isDeleted columnb in gwtbb_thumb
			}
			state.orderedPictures = orderedPictures;
			state.createPictures = createPictures;
			state.updatePictures = updatePictures;
			state.pictureSnaps = pictureSnaps;
			state.createThumbs = createThumbs;
			state.updateThumbs = updateThumbs;
			state.thumbSnaps = thumbSnaps;
		}
	}

	public void persist(UpdateState state) {
		if (state.isPersistable()) {
			pictureDao.createPreIdentified(state.createPictures);
			pictureDao.update(state.updatePictures);
			pictureSnapDao.create(state.pictureSnaps);
			thumbDao.createPreIdentified(state.createThumbs);
			thumbDao.update(state.updateThumbs);
			thumbSnapDao.create(state.thumbSnaps);
		}
	}

	public void persist(CreateState state) {
		if (state.isPersistable()) {
			pictureDao.createPreIdentified(state.createPictures);
			pictureSnapDao.create(state.pictureSnaps);
			thumbDao.createPreIdentified(state.createThumbs);
			thumbSnapDao.create(state.thumbSnaps);
		}
	}

	public CreateState validateCreate(Map<FieldId, Failure> failures, int index, List<PictureItemBean> pictureItems) {
		return doValidate(failures, index, pictureItems, new CreateState());
	}

	public UpdateState validateUpdate(Map<FieldId, Failure> failures, int index, List<PictureItemBean> pictureItems) {
		return doValidate(failures, index, pictureItems, new UpdateState());
	}

	private <T extends State> T doValidate(Map<FieldId, Failure> failures, int index, List<PictureItemBean> pictureItems, T state) {
		if (pictureItems != null) {
			state.pictureItems = pictureItems;
			state.pictureItemsSize = pictureItems.size();
			for (int i = 0; i < state.pictureItemsSize; i++) {
				PictureItemBean pib = pictureItems.get(i);
				pib.validate(failures, index);
				String fileName = pib.getFileName();
				if (StringUtils.isNotEmpty(fileName)) {
					if (pictureFileOrganizer.fileExists(fileName))
						state.numPictures++;
					else {
						failures.put(FieldId.create(Picture.PICTURE_ID, index), new Failure(GwtbbSmess.instance().fileNotUploaded()));
						state.failed = true;
					}
				}
			}
		}
		return state;
	}

	private static PictureSnap newPictureSnapshot(Picture picture, User actor, String process) {
		return Meta.instantiateSnapshot(PictureSnap.class, picture, PictureBase.PROPERTIES, actor.getId(), process);
	}

	private static ThumbSnap newThumbSnapshot(Thumb thumb, User actor, String process) {
		return Meta.instantiateSnapshot(ThumbSnap.class, thumb, ThumbBase.PROPERTIES, actor.getId(), process);
	}

	public static class CreateState extends State {
	}

	public static class UpdateState extends State {
		protected List<Map<String, Object>> updatePictures;
		protected List<Map<String, Object>> updateThumbs;

		public List<Map<String, Object>> getUpdatePictures() {
			return updatePictures;
		}

		public List<Map<String, Object>> getUpdateThumbs() {
			return updateThumbs;
		}

		@Override
		public boolean isEmpty() {
			return !isPersistable() || (createPictures.isEmpty() && createThumbs.isEmpty() && updatePictures.isEmpty() && updateThumbs.isEmpty());
		}

		@Override
		public boolean isPersistable() {
			return super.isPersistable() && updatePictures != null && updateThumbs != null;
		}
	}

	public static class State {
		protected boolean failed;
		protected int numPictures;
		protected List<PictureItemBean> pictureItems;
		protected int pictureItemsSize;
		protected List<Picture> orderedPictures;
		protected List<Picture> createPictures;
		protected List<Thumb> createThumbs;
		protected List<PictureSnap> pictureSnaps;
		protected List<ThumbSnap> thumbSnaps;
		protected Object other;

		public Object getOther() {
			return other;
		}

		public void setOther(Object other) {
			this.other = other;
		}

		public List<Picture> getCreatePictures() {
			return createPictures;
		}

		public List<Thumb> getCreateThumbs() {
			return createThumbs;
		}

		public int getNumPictures() {
			return numPictures;
		}

		/**
		 * The order in this list should be used to store any PictureRelations
		 *
		 * @return the list of pictures in the order the relations should be stored
		 */
		public List<Picture> getOrderedPictures() {
			return orderedPictures;
		}

		public List<PictureItemBean> getPictureItems() {
			return pictureItems;
		}

		public int getPictureItemsSize() {
			return pictureItemsSize;
		}

		public List<PictureSnap> getPictureSnaps() {
			return pictureSnaps;
		}

		public List<ThumbSnap> getThumbSnaps() {
			return thumbSnaps;
		}

		public boolean isFailed() {
			return failed;
		}

		public boolean isEmpty() {
			return !isPersistable() || (createPictures.isEmpty() && createThumbs.isEmpty());
		}

		public boolean isInstantiatable() {
			return !failed && numPictures > 0;
		}

		public boolean isPersistable() {
			return createPictures != null && pictureSnaps != null && createThumbs != null && thumbSnaps != null;
		}
	}
}
